home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Graphics / nxyplot / Source / Plot.m < prev    next >
Text File  |  1994-02-01  |  68KB  |  2,325 lines

  1.  
  2. /* Generated by Interface Builder */
  3.  
  4. #import "defs.h"
  5. #import "Plot.h"
  6. #import <appkit/appkit.h>
  7. #import <objc/Storage.h>
  8. #import <math.h>        /* for MAXFLOAT, etc. */
  9. #import <strings.h>
  10. #import <streams/streams.h>
  11. #import "ColumnSelectionHandler.h"
  12. #import "ErrorBarHandler.h"
  13. #import <defaults/defaults.h>
  14.  
  15. /* The following routines are in auxil.m: */
  16. extern void computeNiceLinInc(float *, float *, float *);
  17. extern void computeNiceLogInc(float *, float *, float *);
  18.  
  19. @implementation Plot
  20.  
  21. - makeSomeScrollWindows
  22. {
  23.   // the following lines exist only to initialize the ScrollWindows.....pdhowell
  24.   NXSize minsize = {0.0,0.0};
  25.   
  26.   [lineMatrixWindow   becomeScrollWindow];
  27.   [symbolMatrixWindow becomeScrollWindow];
  28.   [legendFormWindow   becomeScrollWindow];
  29.  
  30.   [lineMatrixWindow   setMinFrameSize:minsize];
  31.   [symbolMatrixWindow setMinFrameSize:minsize];
  32.   [legendFormWindow   setMinFrameSize:minsize];
  33.  
  34.   return self;
  35. }
  36.  
  37. + initialize
  38. {
  39.   const NXDefaultsVector nxyplotDefaults = {
  40.     { "colorOption", "NO"},
  41.     { "colorPrinting", "NO"},
  42.     { "cycleLineStyles", "NO"},
  43.     { "opaqueBackground", "YES"},
  44.     { "autoPlot", "YES"},
  45.     { NULL, NULL}
  46.   };
  47.  
  48.   self = [[Object alloc] init];
  49.   NXRegisterDefaults("nxyplot", nxyplotDefaults);
  50.   return self;
  51. }
  52.  
  53. - init
  54. {
  55.   // Initialize variables here:
  56.   nfilestotal = 0;
  57.   ncurvestotal = 0;
  58.   globaldatamin.x = MAXFLOAT;
  59.   globaldatamin.y = MAXFLOAT;
  60.   globaldatamax.x = -MAXFLOAT;
  61.   globaldatamax.y = -MAXFLOAT;
  62.   beepError = 0;
  63.   backgroundcolor = NX_COLORWHITE;
  64.   textcolor = NX_COLORBLACK;
  65.   srandom(10);            /* initialize for color selection  */
  66.   oldMin.x = 0.0;
  67.   oldMin.y = 0.0;
  68.   oldMax.x = 0.0;
  69.   oldMax.y = 0.0;
  70.   oldInc.x = 0.0;
  71.   oldInc.y = 0.0;
  72.   currentMin.x = 0.0;
  73.   currentMin.y = 0.0;
  74.   currentMax.x = 0.0;
  75.   currentMax.y = 0.0;
  76.   currentInc.x = 0.0;
  77.   currentInc.y = 0.0;
  78.  
  79.   return self;
  80. }
  81.  
  82. // Delete all data (free up the space that was malloc'ed)
  83. - removeAllFiles:sender
  84. {
  85.   int n, j;
  86.   datahunk *pdh;
  87.   const char * generictitle = "NXYPLOT";
  88.  
  89.   if (nfilestotal == 0) {
  90.     return self;
  91.   }
  92.  
  93.   // Put up an alert panel.  This method is called by a menu item and also
  94.   // by the removeAndOpen method; only if it's called by the menu item
  95.   // do we want to put up the alert panel.
  96.   if (sender != self) {
  97.     if (NXRunAlertPanel("Remove all", "Remove all files and clear plot",
  98.             "OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
  99.       return self;
  100.     }
  101.   }
  102.  
  103.   for (n=nfilestotal-1; n>=0; n--) {
  104.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  105.     for (j = 0; j < pdh->ncurves; j++) {
  106.       free( (void *)*(pdh->y+j) );
  107.     }
  108.     free( (void *)(pdh->y) );
  109.     if (pdh->has_eybars) {
  110.       for (j = 0; j < pdh->ncurves; j++) {
  111.         free( (void *)*(pdh->ey+j) );
  112.       }
  113.       free( (void *)(pdh->ey));
  114.     }
  115.     free( (void *)(pdh->x) );
  116.     if (pdh->has_exbars) {
  117.       free( (void *)(pdh->ex));
  118.     }
  119.     free( (void *)(pdh->filename) );
  120.   }
  121.   [datahunkArray empty];
  122.   [self adjustPanels:ncurvestotal :-1]; /* -1 is a special signal */
  123.   nfilestotal = 0;
  124.  
  125.   [columnSelectionHandler removeAll:self];
  126.  
  127.   [errorBarHandler removeAll:self];
  128.  
  129.   [canvas display];        /* clear the canvas */
  130.  
  131.   [[canvas window] setTitle:generictitle];
  132.  
  133.   ncurvestotal = 0;
  134.  
  135.   // reset globaldatamin/max
  136.   globaldatamin.x = MAXFLOAT;
  137.   globaldatamin.y = MAXFLOAT;
  138.   globaldatamax.x = -MAXFLOAT;
  139.   globaldatamax.y = -MAXFLOAT;
  140.  
  141.   // clear xMin/Max/Inc and yMin/Max/Inc windows:
  142.   [xLimits setStringValue:"" at:0];
  143.   [xLimits setStringValue:"" at:1];
  144.   [xLimits setStringValue:"" at:2];
  145.   [yLimits setStringValue:"" at:0];
  146.   [yLimits setStringValue:"" at:1];
  147.   [yLimits setStringValue:"" at:2];
  148.   srandom(10);            /* initialize for color selection  */
  149.  
  150.   oldMin.x = 0.0;
  151.   oldMin.y = 0.0;
  152.   oldMax.x = 0.0;
  153.   oldMax.y = 0.0;
  154.   oldInc.x = 0.0;
  155.   oldInc.y = 0.0;
  156.   currentMin.x = 0.0;
  157.   currentMin.y = 0.0;
  158.   currentMax.x = 0.0;
  159.   currentMax.y = 0.0;
  160.   currentInc.x = 0.0;
  161.   currentInc.y = 0.0;
  162.  
  163.   return self;
  164. }
  165.  
  166. // Remove all existing files and open a new one
  167. - removeAndOpen:sender
  168. {
  169.   if (NXRunAlertPanel("New",
  170.               "Remove all files, clear plot\nand open new file",
  171.               "OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
  172.     return self;
  173.   }
  174.  
  175.   [self removeAllFiles:self];
  176.   [self open:self];
  177.   return self;
  178. }
  179.  
  180.  
  181. - fixFileRemovalPanel:sender
  182. {
  183.   int n;
  184.   char title[80];
  185.   NXCoord dy;
  186.   int numrows, numcols;
  187.   NXSize cellsize, intercell;
  188.  
  189.   // Fix up the filename matrix
  190.   [fileRemovalMatrix getNumRows:&numrows numCols:&numcols];
  191.   [fileRemovalMatrix getCellSize:&cellsize];
  192.   [fileRemovalMatrix getIntercell:&intercell];
  193.  
  194.   [fileRemovalMatrix renewRows:nfilestotal cols:1];
  195.   dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  196.   for (n=0; n<nfilestotal; n++) {
  197.     if (!strncmp([self filename:(unsigned)n], "pasteboard", 10))
  198.       sprintf(title, "pasteboard");
  199.     else
  200.       sprintf(title, strrchr([self filename:(unsigned)n], '/') + 1);
  201.     [[fileRemovalMatrix cellAt:n :0] setStringValue:title];
  202.   }
  203.   [fileRemovalMatrix sizeToCells];
  204.   [fileRemovalMatrix moveBy:0.0 :-dy];
  205.  
  206.   // Fix up the buttons that go with the matrix of names
  207.   [fileRemovalButtons getCellSize:&cellsize];
  208.   [fileRemovalButtons getIntercell:&intercell];
  209.   dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  210.   [fileRemovalButtons renewRows:nfilestotal cols:1];
  211.   [fileRemovalButtons sizeToCells];
  212.   [fileRemovalButtons moveBy:0.0 :-dy];
  213.   for (n=0; n<nfilestotal; n++) {
  214.     [ [fileRemovalButtons cellAt:n :0] setState:0]; /* a safety play */
  215.   }
  216.  
  217.   [fileRemovalPanel display];
  218.  
  219.   if (sender != self) {
  220.     /* If this method is called from the menu, make the window key. */
  221.     [fileRemovalPanel makeKeyAndOrderFront:self];
  222.   }
  223.  
  224.   return self;
  225. }
  226.  
  227.  
  228. // Remove some existing files; the file removal is not hard, but correctly
  229. // updating the linestyle, symbolstyle, and legend matrices is harder.
  230. // Correctly handling the ColumnSelection and ErrorBar panels is even harder.
  231. - removeSomeFiles:sender
  232. {
  233.   int n, i, j;
  234.   int old_index = 0, current_index = 0;
  235.   /* These integers point to the columns of the linestyle and symbolstyle
  236.    * matrices as we run along updating the matrices.  They also serve in
  237.    * updating the curvecolors array.
  238.    */
  239.   datahunk *pdh;
  240.   int *newlinestyles, *newsymbolstyles;
  241.  
  242.   if (nfilestotal == 0) {
  243.     return self;
  244.   }
  245.   /*
  246.    * If all files are marked for deletion, we will call the "removeAllFiles"
  247.    * method.  So check here to see if all files are marked for deletion.
  248.    */
  249.   i = 1;
  250.   for (n=0; n < nfilestotal; n++) {
  251.     if ([[fileRemovalButtons cellAt:n :0] state] == 0) {
  252.       i = 0;            // there is a file not being deleted
  253.       break;
  254.     }
  255.   }
  256.   if (i == 1) {            // all files are to be deleted
  257.     [self removeAllFiles:self];
  258.     return self;
  259.   }
  260.  
  261.  
  262.   /* Figure out what the linestyle and symbolstyle matrices should look
  263.    * like after the file removal process.  We do it this way because simply
  264.    * copying the columns of the matrices fails after the renewRows:cols:
  265.    * message is sent to them.
  266.    */
  267.   newlinestyles = (int *)malloc(ncurvestotal * sizeof(int));
  268.   newsymbolstyles = (int *)malloc(ncurvestotal * sizeof(int));
  269.   for (n=0; n < nfilestotal; n++) {
  270.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
  271.       /* Just bump the index if the file is to be deleted.  */
  272.       old_index += [self nCurves:n];
  273.     }
  274.     else {
  275.       for (i=0; i < [self nCurves:n]; i++) {
  276.     for (j=0; j < N_LINE_STYLES; j++) {
  277.       if ([ [lineMatrix cellAt:j :old_index] state] == 1) {
  278.         newlinestyles[current_index] = j;
  279.         break;
  280.       }
  281.     }
  282.     for (j=0; j < N_SYMBOL_STYLES; j++) {
  283.       if ([ [symbolMatrix cellAt:j :old_index] state] == 1) {
  284.         newsymbolstyles[current_index] = j;
  285.         break;
  286.       }
  287.     }
  288.     /* Update the curvecolors array: */
  289.     curvecolors[current_index] = curvecolors[old_index];
  290.     old_index++;
  291.     current_index++;
  292.       }
  293.     }
  294.   }
  295.   /* We could do a realloc on curvecolors here, but not much would be saved. */
  296.  
  297.   /* Reset the running indices. */
  298.   old_index = 0;
  299.   current_index = 0;
  300.  
  301.   for (n=0; n < nfilestotal; n++) {
  302.     /* Is the nth file marked for deletion?  */
  303.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {    /* yes it is */
  304.       pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  305.       for (j = 0; j < pdh->ncurves; j++) {
  306.     free( (void *)*(pdh->y+j) );
  307.       }
  308.       free( (void *)(pdh->y) );
  309.       if (pdh->has_eybars) {
  310.     for (j = 0; j < pdh->ncurves; j++) {
  311.       free( (void *)*(pdh->ey+j) );
  312.     }
  313.     free( (void *)(pdh->ey));
  314.       }
  315.       free( (void *)(pdh->x) );
  316.       if (pdh->has_exbars) {
  317.     free( (void *)(pdh->ex));
  318.       }
  319.       free( (void *)(pdh->filename) );
  320.       old_index += [self nCurves:n];
  321.     }
  322.     else {
  323.       /* Column copying of the linestyle and symbolstyle matrices
  324.        * is handled later.  Do the legend form here.
  325.        */
  326.       for (j=0; j < [self nCurves:n]; j++) {
  327.     [legendForm setStringValue:[legendForm stringValueAt:old_index]
  328.                     at:current_index];
  329.     [legendForm drawCellAt:current_index];
  330.     old_index++;
  331.     current_index++;
  332.       }
  333.     }
  334.   }
  335.  
  336.   /* Get rid of extraneous legendForm entries: */
  337.   for (j=current_index; j<ncurvestotal; j++) {
  338.     [legendForm removeEntryAt:j];
  339.   }
  340.   [legendForm sizeToFit];
  341.   [ [legendForm window] display];
  342.  
  343.   /*
  344.    * Do the ColumnSelectionHandler manipulation here, before nfilestotal
  345.    * gets reset.
  346.    */
  347.   [columnSelectionHandler update:self];
  348.   [errorBarHandler update:self];
  349.  
  350.   /* We put the datahunkArray manipulation here because the removeAt method
  351.    * shifts the elements of the datahunkArray to close the gap created by
  352.    * removing one element; for this reason we count down rather than up.
  353.    */
  354.   j = nfilestotal;
  355.   for (n=nfilestotal-1; n >= 0; n--) {
  356.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
  357.       [datahunkArray removeElementAt:(unsigned)n];
  358.       j--;
  359.     }
  360.   }
  361.   nfilestotal = j;
  362.   ncurvestotal = current_index;
  363.  
  364.   /* Now resize and display the linestyle and symbolstyle matrices */
  365.   [lineMatrix renewRows:N_LINE_STYLES cols:ncurvestotal];
  366.   [lineMatrix sizeToCells];
  367.   for (i=0; i<ncurvestotal; i++) {
  368.     for (j=0; j<N_LINE_STYLES; j++) {
  369.       [ [lineMatrix cellAt:j :i] setState:0];
  370.     }
  371.     [ [lineMatrix cellAt:newlinestyles[i] :i] setState:1];
  372.   }
  373.  
  374.   [lineText renewRows:1 cols:ncurvestotal];
  375.   [ [lineMatrix window] display];
  376.  
  377.   [symbolMatrix renewRows:N_SYMBOL_STYLES cols:ncurvestotal];
  378.   [symbolMatrix sizeToCells];
  379.   for (i=0; i<ncurvestotal; i++) {
  380.     for (j=0; j<N_SYMBOL_STYLES; j++) {
  381.       [ [symbolMatrix cellAt:j :i] setState:0];
  382.     }
  383.     [ [symbolMatrix cellAt:newsymbolstyles[i] :i] setState:1];
  384.   }
  385.   [symbolText renewRows:1 cols:ncurvestotal];
  386.   [ [symbolMatrix window] display];
  387.  
  388.   [self adjustScrollWindows];
  389.  
  390.   /* must also fix up appearence of the file removal window */
  391.   [self fixFileRemovalPanel:self];
  392.   [fileRemovalPanel performClose:self];
  393.   free((void *)newlinestyles);
  394.   free((void *)newsymbolstyles);
  395.  
  396.   /* and replot automatically */
  397.   if (nfilestotal == 0) {
  398.     [canvas display];
  399.   }
  400.   else {
  401.     [self drawPlot:self];
  402.   }
  403.  
  404.   return self;
  405. }
  406.  
  407.  
  408. /* return the x data for the nth file */
  409. - (NXCoord *)xdata:(int)n
  410. {
  411.   datahunk *pdh;
  412.  
  413.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  414.   return pdh->x;
  415. }
  416.  
  417. /* return the y data for the nth file */
  418. - (NXCoord **)ydata:(int)n
  419. {
  420.   datahunk *pdh;
  421.  
  422.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  423.   return pdh->y;
  424. }
  425.  
  426. /* return the y error data for the nth file */
  427. - (NXCoord **)eydata:(int)n
  428. {
  429.   datahunk *pdh;
  430.  
  431.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  432.   return pdh->ey;
  433. }
  434.  
  435. /* return the x error data for the nth file */
  436. - (NXCoord *)exdata:(int)n
  437. {
  438.   datahunk *pdh;
  439.  
  440.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  441.   return pdh->ex;
  442. }
  443.  
  444. /* return the number of x-points in the nth file */
  445. - (int)nPoints:(int)n
  446. {
  447.   datahunk *pdh;
  448.  
  449.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  450.   return pdh->npoints;
  451. }
  452.  
  453. /* return the number of curves in the nth file */
  454. - (int)nCurves:(int)n
  455. {
  456.   datahunk *pdh;
  457.  
  458.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  459.   return pdh->ncurves;
  460. }
  461.  
  462. /* return the name of the nth file */
  463. - (char *)filename:(unsigned)n
  464. {
  465.   datahunk *pdh;
  466.  
  467.   pdh = (datahunk *)[datahunkArray elementAt:n];
  468.   return pdh->filename;
  469. }
  470.  
  471. /* Does the nth file have error bars in y? */
  472. - (BOOL) has_eybars:(int)n
  473. {
  474.   datahunk *pdh;
  475.  
  476.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  477.   return pdh->has_eybars;
  478. }
  479.  
  480. /* Does the nth file have error bars in x? */
  481. - (BOOL) has_exbars:(int)n
  482. {
  483.   datahunk *pdh;
  484.  
  485.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  486.   return pdh->has_exbars;
  487. }
  488.  
  489. - (int)nCurvesTotal     { return ncurvestotal;}
  490.  
  491. - (int)nFiles           { return nfilestotal;}
  492.  
  493. - makeLineStyle:(int)aCurve :(int)lineStyle
  494. {
  495.   int   row;
  496.  
  497.   for (row = 0; row < N_LINE_STYLES; row++)
  498.     [[lineMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  499.   [[lineMatrix cellAt:lineStyle :aCurve] setState:1];
  500.   return self;
  501. }
  502.  
  503. - makeSymbolType:(int)aCurve :(int)symType;
  504. {
  505.   int   row;
  506.  
  507.   for (row = 0; row < N_SYMBOL_STYLES; row++)
  508.     [[symbolMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  509.   [[symbolMatrix cellAt:symType :aCurve] setState:1];
  510.   return self;
  511. }
  512.  
  513. - (BOOL) xaxisLog
  514. {
  515.   if ( [xLinLog state] ) return YES;
  516.   else return NO;
  517. }
  518.  
  519. - forceXaxisLinear
  520. {
  521.   [xLinLog setState:0];
  522.   [xLinLog display];
  523.   return self;
  524. }
  525.  
  526. - forceXaxisLog
  527. {
  528.   [xLinLog setState:1];
  529.   [xLinLog display];
  530.   return self;
  531. }
  532.  
  533. - (BOOL) yaxisLog
  534. {
  535.   if ( [yLinLog state] ) return YES;
  536.   else return NO;
  537. }
  538.  
  539. - forceYaxisLinear
  540. {
  541.   [yLinLog setState:0];
  542.   [yLinLog display];
  543.   return self;
  544. }
  545.  
  546. - forceYaxisLog
  547. {
  548.   [yLinLog setState:1];
  549.   [yLinLog display];
  550.   return self;
  551. }
  552.  
  553. - (BOOL) shouldChangeLegendFont
  554. {
  555.   if ( [changeLegendFont state] ) return YES;
  556.   else return NO;
  557. }
  558.  
  559. - (BOOL) shouldChangeLegendTitleFont
  560. {
  561.   if ( [changeLegendTitleFont state] ) return YES;
  562.   else return NO;
  563. }
  564.  
  565. - (BOOL) shouldChangeMainTitleFont
  566. {
  567.   if ( [changeMainTitleFont state] ) return YES;
  568.   else return NO;
  569. }
  570.  
  571. - (BOOL) shouldChangeYTitleFont
  572. {
  573.   if ( [changeYTitleFont state] ) return YES;
  574.   else return NO;
  575. }
  576.  
  577. - (BOOL) shouldChangeXTitleFont
  578. {
  579.   if ( [changeXTitleFont state] ) return YES;
  580.   else return NO;
  581. }
  582.  
  583. - (BOOL) shouldChangeTicLabelFont
  584. {
  585.   if ( [changeTicLabelFont state] ) return YES;
  586.   else return NO;
  587. }
  588.  
  589. - (int)providelinestyle:(int)aCurve
  590. {
  591.   int   row, cellstate;
  592.  
  593.   /* First, if line is turned off, return that */
  594.   if ([ [lineMatrix cellAt:N_LINE_STYLES-1 :aCurve] state] == 1) {
  595.     return N_LINE_STYLES-1;
  596.   }
  597.   /*
  598.    * Next, check if we are printing or previewing and if we should
  599.    * cycle the line styles
  600.    */
  601.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  602.       && ([accPrintLineStyleButton state] == 1) ) {
  603.     return aCurve % (N_LINE_STYLES - 1);
  604.   }
  605.   /*
  606.    * Here we can just look at the linestyle matrix.
  607.    */
  608.   else {
  609.     for (row = 0; row < N_LINE_STYLES; row++) {
  610.       cellstate = [ [lineMatrix cellAt:row :aCurve] state];
  611.       if (cellstate == 1) return row;
  612.     }
  613.   }
  614.   return 0;            /* for safety */
  615. }
  616.  
  617. - (int)providesymbolstyle:(int)aCurve
  618. {
  619.   int   row, cellstate;
  620.  
  621.   for (row = 0; row < N_SYMBOL_STYLES; row++) {
  622.     cellstate = [ [symbolMatrix cellAt:row :aCurve] state];
  623.     if (cellstate == 1) return row;
  624.   }
  625.   return 0;            /* for safety */
  626. }
  627.  
  628. /*
  629.  * Changed floats to doubles in the following group.  With floats, when
  630.  * the value was 250.4, the value returned by floatValueAt: was
  631.  * 250.399994 (for example).  (This appears to be a problem with the
  632.  * atof function on Unix 32-bit systems.)  This gave troubles in the
  633.  * tic mark routine in PlotView.m.  
  634.  */
  635. - (double)provideXmin  {return [xLimits doubleValueAt:0];}
  636. - (double)provideXmax  {return [xLimits doubleValueAt:1];}
  637. - (double)provideXinc  {return [xLimits doubleValueAt:2];}
  638. - (double)provideYmin  {return [yLimits doubleValueAt:0];}
  639. - (double)provideYmax  {return [yLimits doubleValueAt:1];}
  640. - (double)provideYinc  {return [yLimits doubleValueAt:2];}
  641.  
  642. - resetXmin:(double)aNum { [xLimits setDoubleValue:aNum at:0]; return self; }
  643. - resetXmax:(double)aNum { [xLimits setDoubleValue:aNum at:1]; return self; }
  644. - resetXinc:(double)aNum { [xLimits setDoubleValue:aNum at:2]; return self; }
  645. - resetYmin:(double)aNum { [yLimits setDoubleValue:aNum at:0]; return self; }
  646. - resetYmax:(double)aNum { [yLimits setDoubleValue:aNum at:1]; return self; }
  647. - resetYinc:(double)aNum { [yLimits setDoubleValue:aNum at:2]; return self; }
  648.  
  649. - (float)provideGlobalXmin {return globaldatamin.x;}
  650. - (float)provideGlobalYmin {return globaldatamin.y;}
  651.  
  652. - resetMinMax:sender
  653. {
  654.   int n;
  655.   datahunk * pdh;
  656.  
  657.   // We have to go through all the data and recalculate min/max since
  658.   // some data curves may have been "turned off" (by setting their linestyle
  659.   // to none and symbolstyle to none).
  660.  
  661.   for (n=0; n<nfilestotal; n++) {
  662.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  663.     [self findMinMax:pdh];
  664.   }
  665.   [self findGlobalMinMax];
  666.  
  667.   [self niceMinMaxInc];
  668.   [self drawPlot:self];        /* redraw plot automatically */
  669.   return self;
  670. }
  671.  
  672. - drawPlotButton:(int)state
  673. {
  674.   if (state==0) {
  675.     [plotButton highlight:NO];    /* will display normal title */
  676.   }
  677.   if (state==1) {
  678.     [plotButton highlight:YES];    /* will display alternate title */
  679.   }
  680.   return self;
  681. }
  682.  
  683. // We make the plotParam object responsible for checking parameters
  684. // before the PlotView object is called.  Thus the PlotView object can
  685. // assume the parameters are OK, and it doesn't have to do any checking.
  686. // Things to check: xinc has the same sign as xmax-xmin (same for y);
  687. // no negative data if log plot requested on x or y axis; there won't be
  688. // too many tic marks requested.
  689. - sanityCheck
  690. {
  691.   float xinc = [self provideXinc];
  692.   float xmax = [self provideXmax], xmin = [self provideXmin];
  693.   float yinc = [self provideYinc];
  694.   float ymax = [self provideYmax], ymin = [self provideYmin];
  695.   int   nticmarks;
  696.  
  697.   /* First check: no nonpositive data if logarithmic axis */
  698.   /* Also check that increment is > 5 (need rint(log10(increment)) >=1 ) */
  699.   if ( [self xaxisLog] ) {
  700.     if (globaldatamin.x <= 0.0 || xmin <= 0.0 || xmax <= 0.0) {
  701.       [xLinLog setState:0];    /* back to linear */
  702.       NXBeep();            /* audible alert */
  703.       beepError = 1;
  704.     }
  705.     if (xinc < 5.0) {
  706.       [self resetXinc:(double)10.0];
  707.       NXBeep();
  708.       beepError = 12;
  709.     }
  710.   }
  711.   if ( [self yaxisLog] ) {
  712.     if (globaldatamin.y <= 0.0 || ymin <= 0.0 || ymax <= 0.0) {
  713.       [yLinLog setState:0];    /* back to linear */
  714.       NXBeep();            /* audible alert */
  715.       beepError = 2;
  716.     }
  717.     if (yinc < 5.0) {
  718.       [self resetYinc:(double)10.0];
  719.       NXBeep();
  720.       beepError = 12;
  721.     }
  722.   }
  723.   /* Second check: xinc has same sign as xmax and xmin */
  724.   if (xinc*(xmax-xmin) <= 0.0) { /* the bad case - avoid infinite loop */
  725.     if (xinc < 0.0) {        /*     in PlotView:drawSelf           */
  726.       xinc = -xinc;
  727.       [self resetXinc:xinc];
  728.       NXBeep();
  729.       beepError = 3;
  730.     }
  731.     if (xmax <= xmin) {
  732.       [self niceMinMaxInc];
  733.       NXBeep();            /* alert */
  734.       beepError = 4;
  735.     }
  736.   }
  737.   /* And similarly for yinc */
  738.   if (yinc*(ymax-ymin) <= 0.0) { /* the bad case - avoid infinite loop */
  739.     if (yinc < 0.0) {
  740.       yinc = -yinc;
  741.       [self resetYinc:yinc];
  742.       NXBeep();            /* alert */
  743.       beepError = 5;
  744.     }
  745.     if (ymax <= ymin) {
  746.       [self niceMinMaxInc];
  747.       NXBeep();            /* alert */
  748.       beepError = 6;
  749.     }
  750.   }
  751.   /* Third check: no more than 100 (say) tic marks on either axis */
  752.   if ( ![self xaxisLog] ) {    /*  linear axis */
  753.     nticmarks = (int) ((xmax - xmin) / xinc) ;
  754.     if (nticmarks > 100) {
  755.       computeNiceLinInc(&xmin, &xmax, &xinc);
  756.       [self resetXmin:xmin];
  757.       [self resetXmax:xmax];
  758.       [self resetXinc:xinc];
  759.       NXBeep();            /* alert */
  760.       beepError = 7;
  761.     }
  762.   }
  763.   if ( ![self yaxisLog] ) {    /*  linear axis */
  764.     nticmarks = (int) ((ymax - ymin) / yinc) ;
  765.     if (nticmarks > 100) {
  766.       computeNiceLinInc(&ymin, &ymax, &yinc);
  767.       [self resetYmin:ymin];
  768.       [self resetYmax:ymax];
  769.       [self resetYinc:yinc];
  770.       NXBeep();            /* alert */
  771.       beepError = 8;
  772.     }
  773.   }
  774.  
  775.   return self;
  776. }
  777.  
  778. - drawPlot:sender
  779. {
  780.   char c_xmin[20], c_xmax[20], c_ymin[20], c_ymax[20], c_xinc[20], c_yinc[20];
  781.   float xmin, xmax, ymin, ymax, xinc, yinc;
  782.  
  783.   if (nfilestotal == 0) return self;
  784.  
  785.   [self drawPlotButton:1];    /* display "Plotting" */
  786.   [self sanityCheck];        /* disallow various bad parameters */
  787.  
  788.   /* maybe save min/max/inc */
  789.   /* This really gets crufty: when we look at the TextField xMin and
  790.    * get its float value via [xLimits floatValueAt:0], we get
  791.    * a float which is the result of applying atof() to a string.  The
  792.    * float which results may not be the same as the float that was originally
  793.    * written into the TextField.  So we apply the process ourselves:
  794.    * float --> string (via sprintf) --> float (via atof).  Ugly, but necessary
  795.    * (otherwise, if you zoom, then hit the Plot button twice, then hit
  796.    * the Previous View button, you might not get back to where you want).
  797.    */
  798.   sprintf(c_xmin,"%g",currentMin.x);
  799.   sprintf(c_xmax,"%g",currentMax.x);
  800.   sprintf(c_xinc,"%g",currentInc.x);
  801.   sprintf(c_ymin,"%g",currentMin.y);
  802.   sprintf(c_ymax,"%g",currentMax.y);
  803.   sprintf(c_yinc,"%g",currentInc.y);
  804.   xmin = (float)atof(c_xmin);
  805.   xmax = (float)atof(c_xmax);
  806.   xinc = (float)atof(c_xinc);
  807.   ymin = (float)atof(c_ymin);
  808.   ymax = (float)atof(c_ymax);
  809.   yinc = (float)atof(c_yinc);
  810.   if ( [xLimits floatValueAt:0] != xmin
  811.       || [xLimits floatValueAt:1] != xmax
  812.       || [xLimits floatValueAt:2] != xinc
  813.       || [yLimits floatValueAt:0] != ymin
  814.       || [yLimits floatValueAt:1] != ymax
  815.       || [yLimits floatValueAt:2] != yinc ) {
  816.     oldMin.x = currentMin.x;
  817.     currentMin.x = [xLimits floatValueAt:0];
  818.     oldMax.x = currentMax.x;
  819.     currentMax.x = [xLimits floatValueAt:1];
  820.     oldInc.x = currentInc.x;
  821.     currentInc.x = [xLimits floatValueAt:2];
  822.     oldMin.y = currentMin.y;
  823.     currentMin.y = [yLimits floatValueAt:0];
  824.     oldMax.y = currentMax.y;
  825.     currentMax.y = [yLimits floatValueAt:1];
  826.     oldInc.y = currentInc.y;
  827.     currentInc.y = [yLimits floatValueAt:2];
  828.   }
  829.  
  830.   [canvas display];
  831.   [self drawPlotButton:0];    /* display "Plot" */
  832.   return self;
  833. }
  834.  
  835. // Allocate enough memory and read the data points
  836. /*
  837.  * This code makes the following assumptions:
  838.  * 1. Any data on a line following the character "!" is to be discarded.
  839.  * 2. We can determine the number of curves by looking at the first
  840.  * line of data, which should be of the form
  841.  *  x  y1  y2    ...    yn
  842.  * (possibly separated by commas, with possible trailing comment).
  843.  * 3. Other lines of the file may contain arbitrary text, but contain no
  844.  * numerals or periods (these get interpreted as floating point numbers
  845.  * when the file is scanned); also, anything after a "!" is discarded.
  846.  *
  847.  * It is not easy to make a completely general and bullet-proof scanning
  848.  * routine.  This code is fairly robust and was easy to write.
  849.  */
  850. - (int) readData:(NXStream *)aDataStream :(char *)fname
  851. {
  852.   BOOL    inword = NO;
  853.   char    c;
  854.   int     j, size = ALLOCSIZE;
  855.   int     tmpint = 0;        /* tmpint initialized to avoid compiler warning */
  856.   int     oldncurves = ncurvestotal;
  857.   datahunk *pdh = (void *)NULL;
  858.   BOOL    noxdata = NO;
  859.   float   tmpfloat = 0.0;    /* initialized to avoid compiler warning */
  860.   
  861.   [self preludeToReading:fname :&pdh];    /* take care of some housekeeping */
  862.   
  863.   /* Figure out the number of curves in the file by reading characters  */
  864.   /* until a newline is encountered; the number of curves is one less   */
  865.   /* than the number of words found.                                    */
  866.   /* We assume the input file is an ascii file; a compressed file will  */
  867.   /* have been pumped through zcat and written to a temporary file.     */
  868.   pdh->ncurves = -1;
  869. top_of_file: ;
  870.   while (1) {
  871.     c = (char)NXGetc(aDataStream);
  872.     if (c == (char)EOF) {
  873.       break;            /* EOF, break out of while loop */
  874.     }
  875.     if (c == '\n') {
  876.       if (pdh->ncurves == -1) {
  877.     goto top_of_file;    // ugh, but there may be a blank line
  878.       }
  879.       else {
  880.     break;            /* we have found some data, break from while loop */
  881.       }
  882.     }
  883.     if (c == '!') {        /* comment signal: start discarding characters */
  884.       while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /*  do nothing */
  885.       if (pdh->ncurves == -1)    /* any data found yet? */
  886.     goto top_of_file;        /* ugh */
  887.       else
  888.     break;
  889.     }
  890.     else if ((inword==NO) && !(c==' ' || c=='\t')) {
  891.       pdh->ncurves++;
  892.       inword = YES;
  893.     }
  894.     else if ((inword==YES) && (c==' ' || c=='\t')) {
  895.       inword = NO;
  896.     }
  897.   }
  898.   if (pdh->ncurves == -1) {    /* couldn't find "\n", give up (after cleanup) */
  899.     [plotButton setAltTitle:"Plotting"]; /* reset plot button */
  900.     [plotButton highlight:NO];
  901.     NXPing();                       /* force redraw */
  902.     free( (void *)(pdh->filename) );
  903.     nfilestotal--;
  904.     return 0;
  905.   }
  906.   if (pdh->ncurves == 0) {    /* only one column, assume x data are integers */
  907.     noxdata = YES;
  908.     tmpfloat = 1.0;
  909.     pdh->ncurves = 1;
  910.     if (pdh->has_exbars || pdh->has_eybars) {
  911.       NXRunAlertPanel("Read Data (with error bars)",
  912.               "Error bars expected, only one curve found\n"
  913.               "Unsetting error bar button and continuing",
  914.               "OK", NULL, NULL);
  915.       [errorBars setTitle:"No error bars"];
  916.       pdh->has_exbars = NO;
  917.       pdh->has_eybars = NO;
  918.     }
  919.   }
  920.  
  921.   /*
  922.    * We read more than one column; if there are error bars we must adjust ncurves.
  923.    *    case            pdh->ncurves        true no. of curves
  924.    *   y only            2n                    n
  925.    *   x only            n (>=2)               n-1
  926.    *   y and x          2n+1 (>=3)             n
  927.    */
  928.   if (pdh->has_eybars && !pdh->has_exbars) {
  929.     if ( pdh->ncurves % 2  !=  0 ) {
  930.       NXRunAlertPanel("Read Data (with error bars)",
  931.               "Strange number of curves found\n"
  932.               "Unsetting error bar button and continuing",
  933.               "OK", NULL, NULL);
  934.       [errorBars setTitle:"No error bars"];
  935.       pdh->has_eybars = NO;
  936.     }
  937.     else {
  938.       pdh->ncurves = pdh->ncurves / 2;
  939.     }
  940.   }
  941.   else if (pdh->has_exbars && !pdh->has_eybars) {
  942.     if ( pdh->ncurves < 2 ) {
  943.       NXRunAlertPanel("Read Data (with error bars)",
  944.               "Too few curves found\n"
  945.               "Unsetting error bar button and continuing",
  946.               "OK", NULL, NULL);
  947.       [errorBars setTitle:"No error bars"];
  948.       pdh->has_exbars = NO;
  949.     }
  950.     else {
  951.       pdh->ncurves--;
  952.     }
  953.   }
  954.   else if (pdh->has_exbars && pdh->has_eybars) {
  955.     if ( pdh->ncurves < 3  ||  pdh->ncurves % 2 == 0 ) {
  956.       NXRunAlertPanel("Read Data (with error bars)",
  957.               "Bad number of curves found\n"
  958.               "Unsetting error bar button and continuing",
  959.               "OK", NULL, NULL);
  960.       [errorBars setTitle:"No error bars"];
  961.       pdh->has_exbars = NO;
  962.       pdh->has_eybars = NO;
  963.     }
  964.     else {
  965.       pdh->ncurves = (pdh->ncurves - 1) / 2;
  966.     }
  967.   }
  968.  
  969.   /* Now read the data into memory */
  970.   NXSeek(aDataStream, 0L, NX_FROMSTART);
  971.     
  972.   pdh->x = (NXCoord *)malloc( size * sizeof(NXCoord) );
  973.   if (pdh->has_exbars) {
  974.     pdh->ex = (NXCoord *)malloc( size * sizeof(NXCoord) );
  975.   }
  976.   pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  977.   for (j = 0; j < pdh->ncurves; j++) {
  978.     *(pdh->y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
  979.   }
  980.   if (pdh->has_eybars) {
  981.     pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  982.     for (j = 0; j < pdh->ncurves; j++) {
  983.       *(pdh->ey+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
  984.     }
  985.   }
  986.   pdh->npoints = 0;
  987.   while(1) {
  988.     if (noxdata) {
  989.       *(pdh->x+pdh->npoints) = tmpfloat++;
  990.     }
  991.     else {
  992.       if (pdh->has_exbars) {    /* x error bars, read two items*/
  993.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
  994.       if (c == '!') {
  995.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  996.         goto skipline;    /* ugh */
  997.       }
  998.     }
  999.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->ex+pdh->npoints)) == 0 ) {
  1000.       if (c == '!') {
  1001.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1002.         goto skipline;    /* ugh */
  1003.       }
  1004.     }
  1005.       }
  1006.       else {            /* no x error bars, read one item */
  1007.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
  1008.       c = (char)NXGetc(aDataStream);    /* throw away extraneous characters */
  1009.       if (c == '!') {
  1010.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1011.         goto skipline;    /* ugh */
  1012.       }
  1013.     }
  1014.       }
  1015.       if (tmpint == EOF) break;    /* break out of the while(1) loop */
  1016.     }
  1017.  
  1018.     if (pdh->has_eybars) {    /* y error bars, read two items */
  1019.       for (j = 0; j < 2 * pdh->ncurves; j++) {
  1020.     if (j%2 == 0) {        /* j is even, reading y value */
  1021.       while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+(j/2))+ pdh->npoints)) == 0 ) {
  1022.         c = (char)NXGetc(aDataStream);    /* throw away the next character */
  1023.         if (c == '!') {        /* comment signal; start discarding characters */
  1024.           while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1025.           goto skipline;    /* ugh */
  1026.         }
  1027.       }
  1028.     }
  1029.     else {            /* j is odd, reading error */
  1030.       while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->ey+(j-1)/2)+ pdh->npoints)) == 0 ) {
  1031.         c = (char)NXGetc(aDataStream);
  1032.         if (c == '!') {
  1033.           while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ;
  1034.           goto skipline;
  1035.         }
  1036.       }
  1037.     }
  1038.       }
  1039.     }
  1040.     else {            /* no error bars, read one item */
  1041.       for (j = 0; j < pdh->ncurves; j++) {
  1042. /*
  1043.  * Try to allow extraneous characters here (if scanf returns 0, which means
  1044.  * it didn't find a number, just do a getc on the input stream to throw that
  1045.  * character away.  This will allow commas and alphabetic characters in the
  1046.  * middle of an input line (no digits or periods, though!).
  1047.  */
  1048.     while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+j)+ pdh->npoints)) == 0 ) {
  1049.       c = (char)NXGetc(aDataStream);    /* throw away the next character */
  1050.       if (c == '!') {        /* comment signal; start discarding characters */
  1051.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1052.         goto skipline;    /* ugh */
  1053.       }
  1054.     }
  1055.       }
  1056.     }
  1057.     if (tmpint == EOF) break;    /* could get this if noxdata==YES */
  1058.     pdh->npoints++;
  1059.     if (pdh->npoints == size) {        /* get more memory */
  1060.       size += ALLOCSIZE;
  1061.       pdh->x = (NXCoord *)realloc(pdh->x, size * sizeof(NXCoord));
  1062.       if (pdh->has_exbars) {
  1063.     pdh->ex = (NXCoord *) realloc(pdh->ex, size * sizeof(NXCoord));
  1064.       }
  1065.       for (j = 0; j < pdh->ncurves; j++) {
  1066.     *(pdh->y+j) = (NXCoord *)realloc( *(pdh->y+j), size * sizeof(NXCoord) );
  1067.       }
  1068.       if (pdh->has_eybars) {
  1069.     for (j = 0; j < pdh->ncurves; j++) {
  1070.       *(pdh->ey+j) = (NXCoord *)realloc( *(pdh->ey+j), size * sizeof(NXCoord) );
  1071.     }
  1072.       }
  1073.     }
  1074. skipline: ;
  1075.   }
  1076.  
  1077.   [self postludeToReading:fname :oldncurves :pdh];
  1078.  
  1079.   ncurvestotal += pdh->ncurves;
  1080.  
  1081.   return pdh->npoints;
  1082. }
  1083.  
  1084. /* Might want to make sure INLINE_MATH is defined when math.h is included
  1085.  * (for guaranteed fast logarithms?)
  1086.  */
  1087. - checkLinLog:(datahunk *)pdh
  1088. {
  1089.   /* Check x and y axes -- do a heuristic test for log/lin.
  1090.    * The test employed comes from M. Merriam and xyplot.
  1091.    */
  1092.   double    scale, linsum, logsum;
  1093.   register  double tmp;
  1094.   int       i, j;
  1095.  
  1096.   /* First test x axis.  */
  1097.   if (pdh->datamax.x > 0.0  &&  pdh->datamin.x > 0.0
  1098.       && pdh->datamax.x != pdh->datamin.x) {
  1099.     scale = fabs( (double)pdh->datamax.x - (double)pdh->datamin.x );
  1100.     linsum = 0.0;
  1101.     for (j=1; j<pdh->npoints; j++) {
  1102.       tmp = ( (double)pdh->x[j]-(double)pdh->x[j-1] )/(double)scale;
  1103.       linsum += tmp*tmp;
  1104.     }
  1105.     scale = log10( (double)pdh->datamax.x/(double)pdh->datamin.x );
  1106.     /* what if datamax.x<datamin.x? */
  1107.     logsum = 0.0;
  1108.     for (i=1; i<pdh->npoints; i++) {
  1109.       tmp = log10( (double)pdh->x[i]/(double)pdh->x[i-1] ) / scale;
  1110.       logsum += tmp*tmp;
  1111.     }
  1112.     if (linsum < logsum) {
  1113.       pdh->xaxislin = YES;    /* linear axis */
  1114.     }
  1115.     else {
  1116.       pdh->xaxislin = NO;    /* logarithmic axis */
  1117.     }
  1118.   }
  1119.   else {
  1120.     pdh->xaxislin = YES;    /* linear */
  1121.   }
  1122.   /* Now test y axis */
  1123.   if (pdh->datamax.y > 0.0  &&  pdh->datamin.y > 0.0
  1124.       && pdh->datamax.y != pdh->datamin.y) {
  1125.     scale = fabs( (double)pdh->datamax.y - (double)pdh->datamin.y );
  1126.     linsum = 0.0;
  1127.     for (j=0; j<pdh->ncurves; j++) {
  1128.       for (i=1; i<pdh->npoints; i++) {
  1129.     tmp = ( (double)*(*(pdh->y+j)+i) - (double)*(*(pdh->y+j)+i-1) )
  1130.       / scale;        /* avoid overflow */
  1131.     linsum += tmp*tmp;
  1132.       }
  1133.     }
  1134.     scale = log10((double)pdh->datamax.y/(double)pdh->datamin.y);
  1135.     logsum = 0.0;
  1136.     for (j=0; j<pdh->ncurves; j++) {
  1137.       for (i=1; i<pdh->npoints;i++) {
  1138.     tmp = log10( (double)*(*(pdh->y+j)+i)/(double)*(*(pdh->y+j)+i-1) ) / scale;
  1139.     logsum += tmp*tmp;
  1140.       }
  1141.     }
  1142.     if (linsum < logsum) {
  1143.       pdh->yaxislin = YES;    /* linear axis */
  1144.     }
  1145.     else {
  1146.       pdh->yaxislin = NO;    /* logarithmic axis */
  1147.     }
  1148.   }
  1149.   else {
  1150.     pdh->yaxislin = YES;    /* linear */
  1151.   }
  1152.   return self;
  1153. }
  1154.  
  1155. // This routine works as follows:
  1156. // If these is just one file, we set the x and y axes to linear or logarithmic
  1157. // depending on our heuristic.
  1158. // If there is more than one file, we don't change the axes unless there
  1159. // would be an illegal result (trying to plot a negative number on a
  1160. // logarithmic axis).
  1161. - checkGlobalLinLog
  1162. {
  1163.   datahunk *pdh;
  1164.  
  1165.   if (nfilestotal == 1) {
  1166.     pdh = (datahunk *)[datahunkArray elementAt:0];
  1167.     if ( pdh->xaxislin )
  1168.       [xLinLog setState:0]; /* linear */
  1169.     else
  1170.       [xLinLog setState:1]; /* logarithmic */ 
  1171.     if ( pdh->yaxislin )
  1172.       [yLinLog setState:0]; /* linear */
  1173.     else
  1174.       [yLinLog setState:1]; /* logarithmic */
  1175.   }
  1176.   else {
  1177.     if ( [self xaxisLog] && globaldatamin.x <= 0.0) {
  1178.       [xLinLog setState:0];    /* back to linear */
  1179.       NXBeep();            /* audible alert */
  1180.       beepError = 9;
  1181.     }
  1182.     if ( [self yaxisLog] && globaldatamin.y <= 0.0) {
  1183.       [yLinLog setState:0];    /* back to linear */
  1184.       NXBeep();            /* audible alert */
  1185.       beepError = 10;
  1186.     }
  1187.   }
  1188.   [xLinLog display];
  1189.   [yLinLog display];
  1190.  
  1191.   return self;
  1192. }
  1193.  
  1194. - adjustLineStyleMatrix:(int)column :(int)row
  1195. {
  1196.   /* Adjust the given column of the lineMatrix object, turning off all
  1197.    * buttons except for the given row.
  1198.    */
  1199.   int i;
  1200.  
  1201.   for (i = 0; i < N_LINE_STYLES; i++) {
  1202.     [ [lineMatrix cellAt:i :column] setState:0];
  1203.   }
  1204.   [ [lineMatrix cellAt:row :column] setState:1];  
  1205.  
  1206.   return self;
  1207. }
  1208.  
  1209. - redisplayLineStyleMatrix
  1210. {
  1211.   [lineMatrix display];
  1212.   return self;
  1213. }
  1214.  
  1215. - adjustSymbolTypeMatrix:(int)column :(int)row
  1216. {
  1217.   /* Adjust the given column of the symbolMatrix object, turning off all
  1218.    * buttons except for the given row.
  1219.    */
  1220.   int i;
  1221.  
  1222.   for (i = 0; i < N_SYMBOL_STYLES; i++) {
  1223.     [ [symbolMatrix cellAt:i :column] setState:0];
  1224.   }
  1225.   [ [symbolMatrix cellAt:row :column] setState:1];  
  1226.   return self;
  1227. }
  1228.  
  1229. - redisplaySymbolTypeMatrix
  1230. {
  1231.   [symbolMatrix display];
  1232.   return self;
  1233. }
  1234.  
  1235. - adjustPanels:(int)oldn :(int)newn
  1236. {
  1237.   /*
  1238.    * Resize the linestyle matrix, the symbolstyle matrix, and
  1239.    * the legend form.
  1240.    * Also arrange to select and highlight only the top button in
  1241.    * each column of the linestyle and symbolstyle matrices.
  1242.    * This code could stand to be cleaned up.
  1243.    */
  1244.   char formtitle[80];
  1245.   int j, k;
  1246.   datahunk *pdh;
  1247.  
  1248.   if (newn == -1) {        /* flag to delete all and start over */
  1249.     for (j=oldn-1; j>=1; j--) {
  1250.       [lineText removeColAt:j andFree:YES];
  1251.       [symbolText removeColAt:j andFree:YES];
  1252.       [lineMatrix removeColAt:j andFree:YES];
  1253.       [symbolMatrix removeColAt:j andFree:YES];
  1254.       [legendForm removeEntryAt:j];
  1255.     }
  1256.     for (j=0; j<1; j++) {
  1257.       if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1258.     [ [lineMatrix cellAt:0 :j] setState:1];
  1259.       if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1260.     [ [symbolMatrix cellAt:0 :j] setState:1];
  1261.       for (k=1; k<N_LINE_STYLES; k++) {
  1262.     [ [lineMatrix cellAt:k :j] setState:0];
  1263.       }
  1264.       for (k=1; k<N_SYMBOL_STYLES; k++) {
  1265.     [ [symbolMatrix cellAt:k :j] setState:0];
  1266.       }
  1267.       sprintf(formtitle, "Curve %d", j+1);
  1268.       [legendForm setStringValue:formtitle at:j];
  1269.       [legendForm drawCellAt:j];
  1270.       sprintf(formtitle, "%d", j+1);
  1271.       [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1272.       [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1273.     }
  1274.     //    [legendTitle setStringValue:"Legend" at:0];  should we uncomment this?
  1275.     //                                          (would need to add an outlet in IB)
  1276.     [lineText sizeToCells];
  1277.     [symbolText sizeToCells];
  1278.     [lineMatrix sizeToCells];
  1279.     [symbolMatrix sizeToCells];
  1280.     [legendForm sizeToFit];
  1281.     [ [lineMatrix window] display];   /* redraw the whole window so extra */
  1282.     [ [symbolMatrix window] display]; /* columns get erased               */
  1283.     [ [legendForm window] display];
  1284.     [curveNumber setIntValue:1]; /* update curve no. on color panel */
  1285.     [curveColorWell setColor:NX_COLORBLACK]; /* and update the color */
  1286.     /* (the background and text color wells are not reset) */
  1287.   }
  1288.  
  1289.   if ( (oldn == 0) && (newn > 1) ) { /* can only happen first time */
  1290.     pdh = [datahunkArray elementAt:0];
  1291.     for (j=1; j<newn; j++) {
  1292.       [lineText addCol];
  1293.       [symbolText addCol];
  1294.       [lineMatrix addCol];
  1295.       if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1296.     [ [lineMatrix cellAt:0 :j] setState:1];
  1297.       /* highlight 1st row new column -- this occurs automatically */
  1298.       [symbolMatrix addCol];
  1299.       if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1300.     [ [symbolMatrix cellAt:0 :j] setState:1];
  1301.       /* highlighting of 1st row new column occurs automatically */
  1302.       sprintf(formtitle, "Curve %d:", j+1);
  1303.       [legendForm addEntry:formtitle];
  1304.       sprintf(formtitle, "%s, Curve %d",
  1305.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1306.                                            : strrchr(pdh->filename,'/') + 1,
  1307.           j+1);
  1308.       [legendForm setStringValue:formtitle at:j];
  1309.       [legendForm drawCellAt:j];
  1310.       sprintf(formtitle, "%d", j+1);
  1311.       [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1312.       [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1313.     }
  1314.     sprintf(formtitle, "%s, Curve 1",
  1315.         strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1316.                                          : strrchr(pdh->filename,'/') + 1);
  1317.     [legendForm setStringValue:formtitle at:0];
  1318.     [legendForm drawCellAt:0];
  1319.     [lineText sizeToCells];
  1320.     [symbolText sizeToCells];
  1321.     [lineMatrix sizeToCells];
  1322.     [symbolMatrix sizeToCells];
  1323.     [legendForm sizeToFit];
  1324.     [lineText display];
  1325.     [symbolText display];
  1326.     [lineMatrix display];    /* don't need to redraw the window here */
  1327.     [symbolMatrix display];
  1328.     [legendForm display];
  1329.   }
  1330.   if ( (oldn == 0) && (newn == 1) ) { /* only for legendForm update */
  1331.     pdh = [datahunkArray elementAt:0];
  1332.     sprintf(formtitle, "%s, Curve 1",
  1333.         strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1334.                                          : strrchr(pdh->filename,'/') + 1);
  1335.     [legendForm setStringValue:formtitle at:0];
  1336.     [legendForm drawCellAt:0];
  1337.     [legendForm display];
  1338.   }
  1339.   if ( oldn != 0 ) {        /* must add columns */
  1340.     pdh = [datahunkArray elementAt:(nfilestotal-1)];
  1341.     for (j=oldn; j<oldn+newn; j++) {
  1342.       [lineText addCol];
  1343.       [symbolText addCol];
  1344.       [lineMatrix addCol];
  1345.       [ [lineMatrix cellAt:0 :j] setState:1];
  1346.       /* highlighting of 1st row new column occurs automatically */
  1347.       [symbolMatrix addCol];
  1348.       [ [symbolMatrix cellAt:0 :j] setState:1];
  1349.       /* highlighting of 1st row new column occurs automatically */
  1350.       sprintf(formtitle, "Curve %d:", j+1);
  1351.       [legendForm addEntry:formtitle];
  1352.       sprintf(formtitle, "%s, Curve %d",
  1353.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1354.                                            : strrchr(pdh->filename,'/') + 1,
  1355.           j+1-oldn);
  1356.       [legendForm setStringValue:formtitle at:j];
  1357.       [legendForm drawCellAt:j];
  1358.       sprintf(formtitle, "%d", j+1);
  1359.       [[lineText cellAt:0 :j] setStringValue:formtitle];
  1360.       [[symbolText cellAt:0 :j] setStringValue:formtitle];
  1361.     }
  1362.     [lineText sizeToCells];
  1363.     [symbolText sizeToCells];
  1364.     [lineMatrix sizeToCells];
  1365.     [symbolMatrix sizeToCells];
  1366.     [legendForm sizeToFit];
  1367.     [lineText display];
  1368.     [symbolText display];
  1369.     [lineMatrix display];    /* don't need to redraw the window here */
  1370.     [symbolMatrix display];
  1371.     [legendForm display];
  1372.   }
  1373.  
  1374.   [self adjustScrollWindows];
  1375.   return self;
  1376. }
  1377.  
  1378. - adjustScrollWindows
  1379. {
  1380. // code from pdhowell for ScrollWindowing
  1381.   NXRect legendFormFrame, lineMatrixFrame, symbolMatrixFrame;
  1382.  
  1383.   [legendForm getFrame:&legendFormFrame];
  1384.   [lineMatrix getFrame:&lineMatrixFrame];
  1385.   [symbolMatrix getFrame:&symbolMatrixFrame];
  1386. // Put MAX in the the following lines because we don't want to allow the windows
  1387. // to get so small that the sliders aren't drawn; the 282 and 288 are the original
  1388. // sizes of the lineMatrixWindow and symbolMatrixWindow -- dcj
  1389.   [[[lineMatrixWindow contentView] docView]
  1390.                                    sizeTo:MAX(128.0+lineMatrixFrame.size.width,282.0)
  1391.                                          :92+lineMatrixFrame.size.height];
  1392.   [[[symbolMatrixWindow contentView] docView]
  1393.                                   sizeTo:MAX(128.0+symbolMatrixFrame.size.width,288.0)
  1394.                                         :92+symbolMatrixFrame.size.height];
  1395.   [[[legendFormWindow contentView] docView]
  1396.                                    sizeTo:32+legendFormFrame.size.width
  1397.                                          :192+legendFormFrame.size.height];
  1398.  
  1399. // Try sending windowDidResize (the "self" is bogus but does no harm) -- dcj
  1400.   [legendFormWindow windowDidResize:self];
  1401.   [lineMatrixWindow windowDidResize:self];
  1402.   [symbolMatrixWindow windowDidResize:self];
  1403.  
  1404. // Try to get the windows to display properly ... dcj
  1405.   [ [lineMatrix window] display];
  1406.   [ [symbolMatrix window] display];
  1407.   [ [legendForm window] display];
  1408.  
  1409.   return self;
  1410. }
  1411.  
  1412. // Go through a particular datahunk and find values for datamin.x,
  1413. // datamax.x, datamin.y, datamax.y
  1414. // We ignore any curves that are "turned off" (linestyle=NONE & symbolstyle=NONE);
  1415. // if all curves in the datahunk are turned off we ignore the x-data, too.
  1416. - findMinMax:(datahunk *)pdh
  1417. {
  1418.   int i, j;
  1419.   datahunk *qdh;
  1420.   int n, ncurve;
  1421.   BOOL ignore;
  1422.  
  1423.   pdh->datamin.x = MAXFLOAT;
  1424.   pdh->datamax.x = -MAXFLOAT;
  1425.   pdh->datamin.y = MAXFLOAT;
  1426.   pdh->datamax.y = -MAXFLOAT;
  1427.  
  1428.   // Find out which curves belong to datahunk pdh
  1429.   ncurve = 0;
  1430.   for (n=0; n<nfilestotal; n++) {
  1431.     qdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1432.     if (qdh == pdh)
  1433.       break;
  1434.     else
  1435.       ncurve += qdh->ncurves;
  1436.   }
  1437.   // Now we know to look at curves ncurve, ncurve+1,...,ncurve+pdh->ncurves-1
  1438.   ignore = YES;
  1439.   for (n = ncurve; n < ncurve + pdh->ncurves; n++) {
  1440.     if ([self providelinestyle:n] != NOLINE
  1441.     || [self providesymbolstyle:n] != NOSYMBOL)
  1442.       ignore = NO;
  1443.   }
  1444.   // Now it's possible we want to ignore all the curves.
  1445.   if (ignore) return self;
  1446.  
  1447.   // If we don't ignore all the curves then we must look at the x data.
  1448.   for (i = 0; i < pdh->npoints; i++)  {
  1449.     pdh->datamin.x = MIN(pdh->datamin.x, pdh->x[i]);
  1450.     pdh->datamax.x = MAX(pdh->datamax.x, pdh->x[i]);
  1451.   }
  1452.   for (j = 0; j < pdh->ncurves; j++) {
  1453.     if ([self providelinestyle:(ncurve+j)] != NOLINE
  1454.     || [self providesymbolstyle:(ncurve+j)] != NOSYMBOL) {
  1455.       for (i = 0; i < pdh->npoints; i++) {
  1456.     pdh->datamin.y = MIN(pdh->datamin.y, *(*(pdh->y+j)+i));
  1457.     pdh->datamax.y = MAX(pdh->datamax.y, *(*(pdh->y+j)+i));
  1458.       }
  1459.     }
  1460.   }
  1461.   return self;
  1462. }
  1463.  
  1464. - findGlobalMinMax
  1465. {
  1466.   int n;
  1467.   datahunk *pdh;
  1468.  
  1469.   globaldatamin.x = MAXFLOAT;
  1470.   globaldatamin.y = MAXFLOAT;
  1471.   globaldatamax.x = -MAXFLOAT;
  1472.   globaldatamax.y = -MAXFLOAT;
  1473.   for (n=0; n<nfilestotal; n++) {
  1474.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1475.     globaldatamin.x = MIN(globaldatamin.x, pdh->datamin.x);
  1476.     globaldatamax.x = MAX(globaldatamax.x, pdh->datamax.x);
  1477.     globaldatamin.y = MIN(globaldatamin.y, pdh->datamin.y);
  1478.     globaldatamax.y = MAX(globaldatamax.y, pdh->datamax.y);
  1479.   }
  1480.   return self;
  1481. }
  1482.  
  1483. // Get pleasing values for the min, max, and increments
  1484. - niceMinMaxInc
  1485. {
  1486.   float fmin, fmax, finc;
  1487.  
  1488.   fmin = globaldatamin.x;
  1489.   fmax = globaldatamax.x;
  1490.   if ([self xaxisLog] ) {
  1491.     computeNiceLogInc(&fmin, &fmax, &finc);
  1492.   }
  1493.   else {
  1494.     computeNiceLinInc(&fmin, &fmax, &finc);
  1495.   }
  1496.   [self resetXmin:fmin];
  1497.   [self resetXmax:fmax];
  1498.   [self resetXinc:finc];
  1499.  
  1500.   fmin = globaldatamin.y;
  1501.   fmax = globaldatamax.y;
  1502.   if ([self yaxisLog] ) {
  1503.     computeNiceLogInc(&fmin, &fmax, &finc);
  1504.   }
  1505.   else {
  1506.     computeNiceLinInc(&fmin, &fmax, &finc);
  1507.   }
  1508.   [self resetYmin:fmin];
  1509.   [self resetYmax:fmax];
  1510.   [self resetYinc:finc];
  1511.   return self;
  1512. }
  1513.  
  1514. // Use the OpenPanel object to get a filename
  1515. - open:sender
  1516. {
  1517.   static const char *const fileTypes[2] = {NULL, NULL};
  1518.                 /* this is supposedly all ASCII files */
  1519.   char  fname[256];
  1520.   id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  1521.  
  1522.   [openPanel setAccessoryView:nil]; /* may have to clean out an accessory view */
  1523.   if (nfilestotal == 0) {
  1524.     if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1525.       [openPanel setTitle:"Open (error bars)"];
  1526.     }
  1527.     else {
  1528.       [openPanel setTitle:"Open"];        /* make sure title is OK (cf. binary open) */
  1529.     }
  1530.   }
  1531.   else {
  1532.     if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1533.       [openPanel setTitle:"Another (error bars)"]; /* 21 character limit here? */
  1534.     }
  1535.     else {
  1536.       [openPanel setTitle:"Open Additional File"];
  1537.     }
  1538.   }
  1539.  
  1540.   if ([openPanel runModalForTypes:fileTypes])  {
  1541.     strncpy(fname, (char *)[openPanel filename], 256);
  1542.     // Check to see if we are trying to open a compressed file
  1543.     // We check for both ".Z" and ".gz"; we are assuming that the
  1544.     // decompression command zcat will handle both of these cases
  1545.     if (fname[strlen(fname)-1]=='Z' && fname[strlen(fname)-2]=='.') {
  1546.       [self handleCompressedFile:fname];
  1547.     }
  1548.     else {
  1549.       [self openFile:fname :fname];
  1550.     }
  1551.   }
  1552.   return self;
  1553. }
  1554.  
  1555. - handleCompressedFile:(char *)fname
  1556. {
  1557.   char  tempfname[256], command[512];
  1558.  
  1559.   // set plot button title:
  1560.   [plotButton setAltTitle:"Uncompressing"];
  1561.   [plotButton highlight:YES];
  1562.   NXPing();            /* force plotButton redraw */
  1563.   // Uncompress the file into a temporary file:
  1564.   strcpy(tempfname, "/tmp/file000000.xyp");
  1565.   NXGetTempFilename(tempfname, 9);
  1566.   sprintf(command, "zcat %s > %s\n", fname, tempfname);
  1567.   // Possible danger ahead: no error checking; what if, e.g,  /tmp is full?
  1568.   system(command);
  1569.   // Now just go ahead and open the temporary file:
  1570.   [self openFile:tempfname :fname];
  1571.   // After returning from the openFile it is safe to unlink:
  1572.   unlink(tempfname);
  1573.   return self;
  1574. }
  1575.  
  1576. - openFile:(char *)dataFile :(char *)realName
  1577. {
  1578.   NXStream *dataStream;
  1579.  
  1580.   if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
  1581.     NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
  1582.     return self;
  1583.   }
  1584.  
  1585.   if ([self readData:dataStream :realName] == 0)  {
  1586.     NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
  1587.             NULL, NULL, dataFile);
  1588.     NXCloseMemory(dataStream, NX_FREEBUFFER);
  1589.     return self;
  1590.   }
  1591.   NXCloseMemory(dataStream, NX_FREEBUFFER);
  1592.   [self plotPrepAndDraw];
  1593.   return self;
  1594. }
  1595.  
  1596. - openBinary:sender
  1597. {
  1598.   static const char *const fileTypes[2] = {NULL, NULL};
  1599.   char  fname[256];
  1600.   id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  1601.  
  1602.   [openPanel setAccessoryView:binaryOpenAccessory];
  1603.   if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1604.     [openPanel setTitle:"Open Binary (error bars)"];
  1605.   }
  1606.   else {
  1607.     [openPanel setTitle:"Open Binary File"];
  1608.   }
  1609.  
  1610.   if ([openPanel runModalForTypes:fileTypes])  {
  1611.     if ([binaryOpenForm intValueAt:0] < 1) {
  1612.       NXRunAlertPanel("Binary Read",
  1613.               "Number of curves is less than 1\n"
  1614.               "Be sure to set this correctly",
  1615.               "OK", NULL, NULL);
  1616.       return self;
  1617.     }
  1618.     strncpy(fname, (char *)[openPanel filename], 256);
  1619.     [self openBinaryFile:fname];
  1620.   }
  1621.   return self;
  1622. }
  1623.  
  1624. #import <sys/stat.h>
  1625. - openBinaryFile:(char *)dataFile
  1626. {
  1627.   struct stat filestat;
  1628.   int    filesize, numpoints;
  1629.   int    numcurves = [binaryOpenForm intValueAt:0];
  1630.   BOOL   xdatathere = [binaryXdatathere state];
  1631.   NXStream *dataStream;
  1632.   int    numcols;        /* number of "columns" expected in the data file */
  1633.  
  1634.   if (strncmp([errorBars title], "No error bars", 13) == 0) {
  1635.     numcols = numcurves;    /* no error bars */
  1636.   }
  1637.   else if (strncmp([errorBars title], "y only", 6) == 0) {
  1638.     numcols = 2*numcurves;
  1639.   }
  1640.   else if (strncmp([errorBars title], "x only", 6) == 0) {
  1641.     numcols = numcurves + 1;
  1642.   }
  1643.   else {
  1644.     numcols = 2*numcurves + 1;
  1645.   }
  1646.  
  1647.   stat(dataFile, &filestat);
  1648.   filesize = filestat.st_size;
  1649.  
  1650.   if (xdatathere) {
  1651.     // This consistency check is not sufficient to guarantee numcurves is
  1652.     // correct, but it's better than nothing.
  1653.     if ( (filesize % (sizeof(NXCoord)*(numcols+1))) != 0 ) {
  1654.       NXRunAlertPanel("Binary Read",
  1655.               "File size inconsistent with number\n"
  1656.               "of curves specified",
  1657.               "OK", NULL, NULL);
  1658.       return self;
  1659.     }
  1660.     numpoints = filesize/(sizeof(NXCoord)*(numcols+1));
  1661.   }
  1662.   else {
  1663.     if ( (filesize % (sizeof(NXCoord)*numcols)) != 0 ) {
  1664.       NXRunAlertPanel("Binary Read",
  1665.               "File size inconsistent with number\n"
  1666.               "of curves specified",
  1667.               "OK", NULL, NULL);
  1668.       return self;
  1669.     }
  1670.     numpoints = filesize/(sizeof(NXCoord)*(numcols));
  1671.   }
  1672.   
  1673.   if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
  1674.     NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
  1675.     return self;
  1676.   }
  1677.  
  1678.   if ([self readBinaryData:dataStream :dataFile :numcurves 
  1679.                           :numpoints :xdatathere] == 0)  {
  1680.     NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
  1681.             NULL, NULL, dataFile);
  1682.     NXCloseMemory(dataStream, NX_FREEBUFFER);
  1683.     return self;
  1684.   }
  1685.   NXCloseMemory(dataStream, NX_FREEBUFFER);
  1686.   [self plotPrepAndDraw];
  1687.  
  1688.   return self;
  1689. }
  1690.  
  1691. - (int)readBinaryData:(NXStream *)aDataStream
  1692.                      :(char *)fname
  1693.                      :(int)numcurves
  1694.                      :(int)numpoints
  1695.                      :(BOOL)xdatathere
  1696. {
  1697.   int      j, oldncurves = ncurvestotal;
  1698.   datahunk *pdh = (void *)NULL;
  1699.   
  1700.   [self preludeToReading:fname :&pdh];    /* take care of some housekeeping */
  1701.   pdh->ncurves = numcurves;
  1702.  
  1703.   /* Now read the data into memory */
  1704.   NXSeek(aDataStream, 0L, NX_FROMSTART);
  1705.     
  1706.   pdh->x = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1707.   if (pdh->has_exbars) {
  1708.     pdh->ex = (NXCoord *)malloc( numpoints * sizeof(NXCoord *) );
  1709.   }
  1710.   pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  1711.   for (j = 0; j < pdh->ncurves; j++) {
  1712.     *(pdh->y+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1713.   }
  1714.   if (pdh->has_eybars) {
  1715.     pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  1716.     for (j = 0; j < pdh->ncurves; j++) {
  1717.       *(pdh->ey+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1718.     }
  1719.   }
  1720.     
  1721.   pdh->npoints = numpoints;
  1722.  
  1723.   if (xdatathere) {
  1724.     NXRead(aDataStream, pdh->x, numpoints*sizeof(NXCoord));
  1725.   }
  1726.   else {
  1727.     for (j=0; j < numpoints; j++) {
  1728.       pdh->x[j] = (float)j;
  1729.     }
  1730.   }
  1731.   if (pdh->has_exbars) {
  1732.     NXRead(aDataStream, pdh->ex, numpoints*sizeof(NXCoord));
  1733.   }
  1734.   for (j = 0; j < pdh->ncurves; j++) {
  1735.     NXRead(aDataStream, *(pdh->y+j), numpoints*sizeof(NXCoord));
  1736.     if (pdh->has_eybars) {
  1737.       NXRead(aDataStream, *(pdh->ey+j), numpoints*sizeof(NXCoord));
  1738.     }
  1739.   }
  1740.  
  1741.   [self postludeToReading:fname :oldncurves :pdh];
  1742.  
  1743.   ncurvestotal += pdh->ncurves;
  1744.  
  1745.   return pdh->npoints;
  1746. }
  1747.  
  1748.  
  1749. - postludeToReading:(char *)fname :(int)oldncurves :(datahunk *)pdh
  1750. {
  1751.   int   j;
  1752.  
  1753.   /* Adjust the lineMatrix, symbolMatrix, and legendForm */
  1754.   [self adjustPanels:oldncurves :pdh->ncurves];
  1755.  
  1756.   if ([columnPanel isVisible])
  1757.     [columnSelectionHandler fixPanel:self];
  1758.  
  1759.   /*
  1760.    * Error bars don't get drawn unless the error bar matrix is correct.
  1761.    * Therefore we don't test on visibility here, rather we test on pdh itself.
  1762.    */
  1763.   if (pdh->has_exbars || pdh->has_eybars)
  1764.     [errorBarHandler updatePanel:self];
  1765.  
  1766.   if ([fileRemovalPanel isVisible])
  1767.     [self fixFileRemovalPanel:self];
  1768.  
  1769.   /* reset plot button */
  1770.   [plotButton setAltTitle:"Plotting"];
  1771.   [plotButton highlight:NO];
  1772.  
  1773.   [self findMinMax:pdh];
  1774.   /*
  1775.    * Don't bother to check lin/log unless this is the first file or
  1776.    * we already have at least one logarithmic axis:
  1777.    */
  1778.   if (nfilestotal==1 || [self xaxisLog] || [self yaxisLog] ) {
  1779.     [self checkLinLog:pdh];
  1780.   }
  1781.   else {
  1782.     pdh->xaxislin = YES;
  1783.     pdh->yaxislin = YES;
  1784.   }
  1785.  
  1786.   curvecolors = (NXColor *)realloc((void *)curvecolors,
  1787.                               (ncurvestotal + pdh->ncurves) * sizeof(NXColor));
  1788.  
  1789.   [[canvas window] setTitleAsFilename:fname];
  1790.  
  1791.   if (!colorOption) {
  1792.     for (j=0; j<pdh->ncurves; j++) {
  1793.       curvecolors[j + ncurvestotal] = NX_COLORBLACK;
  1794.     }
  1795.   }
  1796.   else {
  1797.   [self makeCurvesColorful:pdh]; /* adjust all the curve colors */
  1798.   }
  1799.  
  1800.   return self;
  1801. }
  1802.  
  1803. - preludeToReading:(char *)fname :(datahunk **)pdh
  1804. {
  1805.   datahunk dh;
  1806.  
  1807.   /* set plot button title "Reading" */
  1808.   [plotButton setAltTitle:"Reading"];
  1809.   [plotButton highlight:YES];
  1810.   NXPing();            /* force plotButton redraw */
  1811.   
  1812.   if (nfilestotal == 0) {
  1813.     datahunkArray = [Storage newCount:1
  1814.       elementSize:sizeof(datahunk)
  1815.       description:"{*{float *}{float *}{float **}{float **}iiffff{BOOL}{BOOL}{BOOL}{BOOL}}"];
  1816.     *pdh = (datahunk *)[datahunkArray elementAt:0];
  1817.     if (*pdh == NULL) {
  1818.       NXRunAlertPanel("readData",
  1819.               "Weird error 0: NULL pointer in readData\n"
  1820.               "I can't continue",
  1821.               "OK", NULL, NULL);
  1822.       exit(0);
  1823.     }
  1824.     nfilestotal = 1;
  1825.   }
  1826.   else {
  1827.     [datahunkArray addElement:(void *)&dh];
  1828.     *pdh = (datahunk *)[datahunkArray elementAt:(unsigned)nfilestotal];
  1829.     if (*pdh == NULL) {
  1830.       NXRunAlertPanel("readData",
  1831.               "Weird error 1: NULL pointer in readData\n"
  1832.               "I can't continue",
  1833.               "OK", NULL, NULL);
  1834.       exit(0);
  1835.     }
  1836.     nfilestotal++;
  1837.   }
  1838.   (*pdh)->filename = (char *)malloc(strlen(fname) + 1);
  1839.   strncpy((*pdh)->filename, fname, strlen(fname) + 1);
  1840.  
  1841.   (*pdh)->has_exbars = NO;
  1842.   (*pdh)->has_eybars = NO;
  1843.   if (strncmp([errorBars title], "y only", 6) == 0) {
  1844.     (*pdh)->has_eybars = YES;
  1845.   }
  1846.   else if (strncmp([errorBars title], "x only", 6) == 0) {
  1847.     (*pdh)->has_exbars = YES;
  1848.   }
  1849.   else if (strncmp([errorBars title], "x and y", 7) == 0) {
  1850.     (*pdh)->has_exbars = YES;
  1851.     (*pdh)->has_eybars = YES;
  1852.   }
  1853.  
  1854.   return self;
  1855. }
  1856.  
  1857. - plotPrepAndDraw
  1858. {
  1859.   /* Check for linear or log on x and y axes */
  1860.   [self checkGlobalLinLog];
  1861.  
  1862.   [self findGlobalMinMax];
  1863.  
  1864.   /* Only check and reset min/max if this is the first file */
  1865.   if (nfilestotal == 1) {
  1866.     [self niceMinMaxInc];
  1867.   }
  1868.  
  1869.   if ([autoPlotButton state] == 1) {
  1870.     return self;
  1871.   }
  1872.   else {
  1873.     [self drawPlot:self];
  1874.   }
  1875.     
  1876.   return self;
  1877. }
  1878.  
  1879. - writeDataFiles:sender
  1880. {
  1881.   int i, j, n;
  1882.   datahunk *pdh;
  1883.   NXStream *outputStream;
  1884.   char filename[1024], paneltitle[256];
  1885.   id    savePanel = [[SavePanel new] setRequiredFileType:""];
  1886.  
  1887.   if (nfilestotal == 0) {
  1888.     NXRunAlertPanel("Write Data", "No data", "OK", NULL, NULL);
  1889.   }
  1890.   else {
  1891.     for (n=0; n<nfilestotal; n++) {
  1892.       pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1893.       sprintf(paneltitle, "Save file %d (%s)", n+1,
  1894.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1895.                                            : strrchr(pdh->filename,'/')+1);
  1896.       [savePanel setTitle:paneltitle];
  1897.       [savePanel setAccessoryView:writeDataAccButton];
  1898.       if ([savePanel runModal]) {
  1899.     strncpy(filename, [savePanel filename], 1024);
  1900.     if ((outputStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL) {
  1901.       NXRunAlertPanel("Write Data", "Cannot open memory for file %d",
  1902.               "OK", NULL, NULL, n);
  1903.       return self;
  1904.     }
  1905.     if ([writeDataAccButton state]) { /* ascii write */
  1906.       for (i=0; i<pdh->npoints; i++) {
  1907.         NXPrintf(outputStream, "%g", pdh->x[i]);
  1908.         if (pdh->has_exbars) {
  1909.           NXPrintf(outputStream, " %g", pdh->ex[i]);
  1910.         }
  1911.         for (j=0; j<pdh->ncurves; j++) {
  1912.           NXPrintf(outputStream, " %g", *(*(pdh->y+j)+i));
  1913.           if (pdh->has_eybars) {
  1914.         NXPrintf(outputStream, " %g", *(*(pdh->ey+j)+i));
  1915.           }
  1916.         }
  1917.         NXPrintf(outputStream, "\n");
  1918.       }
  1919.     }
  1920.     else {            /* binary write */
  1921.       NXWrite(outputStream, pdh->x, (pdh->npoints)*sizeof(NXCoord));
  1922.       if (pdh->has_exbars) {
  1923.         NXWrite(outputStream, pdh->ex, (pdh->npoints)*sizeof(NXCoord));
  1924.       }
  1925.       for (j=0; j<pdh->ncurves; j++) {
  1926.         NXWrite(outputStream, *(pdh->y+j), (pdh->npoints)*sizeof(NXCoord));
  1927.         if (pdh->has_eybars) {
  1928.           NXWrite(outputStream, *(pdh->ey+j), (pdh->npoints)*sizeof(NXCoord));
  1929.         }
  1930.       }
  1931.     }
  1932.     NXFlush(outputStream);
  1933.     NXSaveToFile(outputStream, filename);
  1934.     NXClose(outputStream);
  1935.       }
  1936.     }
  1937.   }
  1938.   return self;
  1939. }
  1940.     
  1941. - whyTheBeep:sender
  1942. {
  1943.   switch(beepError) {
  1944.   case 0:
  1945.     NXRunAlertPanel("The Beep Panel", "Press this button if the program beeps\n"
  1946.        "and you want to know why", "OK", NULL, NULL);
  1947.     break;
  1948.   case 1:
  1949.     NXRunAlertPanel("The Beep Happened Because:",
  1950.             "x-axis changed from log to linear", "OK", NULL, NULL);
  1951.     break;
  1952.   case 2:
  1953.     NXRunAlertPanel("The Beep Happened Because:",
  1954.             "y-axis changed from log to linear", "OK", NULL, NULL);
  1955.     break;
  1956.   case 3:
  1957.     NXRunAlertPanel("The Beep Happened Because:",
  1958.             "x-increment was negative\n (I changed it)", "OK", NULL, NULL);
  1959.     break;
  1960.   case 4:
  1961.     NXRunAlertPanel("The Beep Happened Because:",
  1962.             "xmax was less than xmin\n (I changed them)", "OK", NULL, NULL);
  1963.     break;
  1964.   case 5:
  1965.     NXRunAlertPanel("The Beep Happened Because:",
  1966.             "y-increment was negative\n (I changed it)", "OK", NULL, NULL);
  1967.     break;
  1968.   case 6:
  1969.     NXRunAlertPanel("The Beep Happened Because:",
  1970.             "ymax was less than ymin\n (I changed them)", "OK", NULL, NULL);
  1971.     break;
  1972.   case 7:
  1973.     NXRunAlertPanel("The Beep Happened Because:",
  1974.             "Too many tic marks would have been on the x-axis\n"
  1975.             " (I changed the min, max, and/or increment)",
  1976.             "OK", NULL, NULL);
  1977.     break;
  1978.   case 8:
  1979.     NXRunAlertPanel("The Beep Happened Because:",
  1980.             "Too many tic marks would have been on the y-axis\n"
  1981.             " (I changed the min, max, and/or increment)",
  1982.             "OK", NULL, NULL);
  1983.     break;
  1984.   case 9:
  1985.     NXRunAlertPanel("The Beep Happened Because:",
  1986.             "x-axis was logarithmic but some datum was negative\n"
  1987.             " (I changed x-axis to linear)", "OK", NULL, NULL);
  1988.     break;
  1989.   case 10:
  1990.     NXRunAlertPanel("The Beep Happened Because:",
  1991.             "y-axis was logarithmic but some datum was negative\n"
  1992.             " (I changed y-axis to linear)", "OK", NULL, NULL);
  1993.     break;
  1994.   case 11:
  1995.     NXRunAlertPanel("The Beep Happened Because:",
  1996.             "tried to set color of a non-existent curve", "OK", NULL, NULL);
  1997.     break;
  1998.   case 12:
  1999.     NXRunAlertPanel("The Beep Happened Because:",
  2000.             "an increment was too small for a log axis\n"
  2001.             " (I reset it)", "OK", NULL, NULL);
  2002.     break;
  2003.   }
  2004.   return self;
  2005. }
  2006.  
  2007. - (NXColor) provideBackgroundColor
  2008. {
  2009.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2010.       && ([accPrintColorButton state] == 0) ) {
  2011.     return NX_COLORWHITE;
  2012.   }
  2013.   else {
  2014.     return backgroundcolor;
  2015.   }
  2016. }
  2017.  
  2018. - (NXColor) provideTextColor
  2019. {
  2020.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2021.       && ([accPrintColorButton state] == 0) ) {
  2022.     return NX_COLORBLACK;
  2023.   }
  2024.   else {
  2025.     return textcolor;
  2026.   }
  2027. }
  2028.  
  2029. - (NXColor) provideCurveColor:(int)aCurve
  2030. {
  2031.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2032.       && ([accPrintColorButton state] == 0) ) {
  2033.     return NX_COLORBLACK;
  2034.   }
  2035.   else {
  2036.     return curvecolors[aCurve];
  2037.   }
  2038. }
  2039.  
  2040. - setBackgroundColor:sender
  2041. {
  2042.   backgroundcolor = [sender color];
  2043.   return self;
  2044. }
  2045.  
  2046. - forceBackgroundColor:(NXColor) aColor
  2047. {
  2048.   backgroundcolor = aColor;
  2049.   return self;
  2050. }
  2051.  
  2052. - forceTextColor:(NXColor) aColor
  2053. {
  2054.   textcolor = aColor;
  2055.   return self;
  2056. }
  2057.  
  2058. - forceCurveColor:(int)curvenum :(NXColor)aColor
  2059. {
  2060.   if (curvenum >= ncurvestotal) {
  2061.     NXBeep();
  2062.     beepError = 11;
  2063.   }
  2064.   else {
  2065.     curvecolors[curvenum] = aColor;
  2066.   }
  2067.   return self;
  2068. }
  2069.  
  2070.  
  2071. - setTextColor:sender
  2072. {
  2073.   textcolor = [sender color];
  2074.   return self;
  2075. }
  2076.  
  2077. - setCurveColor:sender
  2078. {
  2079.   int curvenum = [curveNumber intValue];
  2080.  
  2081.   if (curvenum > ncurvestotal || curvenum < 1) {
  2082.     NXBeep();
  2083.     beepError = 11;
  2084.     return self;
  2085.   }
  2086. /* Change the color in the color well right away: */
  2087.   [sender setColor:[sender color]];
  2088.   NXPing();            /* make sure it gets displayed */
  2089.   curvecolors[curvenum-1] = [sender color];
  2090. /* Try to be helpful and increment the curvenumber */
  2091.   curvenum = (curvenum == ncurvestotal? 1 : curvenum + 1);
  2092. /* Slight delay (1/2 second) so the well doesn't change instantly.  */
  2093.   usleep((unsigned)500000);
  2094.   [curveNumber setIntValue:curvenum];
  2095. /* Tell the sender (a color well) to go on the next color: */
  2096.   [sender setColor:curvecolors[curvenum - 1]];
  2097.   return self;
  2098. }
  2099.  
  2100. /* Update the curveColorWell if the curve number is changed */
  2101. - textDidEnd:textObject endChar:(unsigned short)whyEnd
  2102. {
  2103.   int curvenum = [curveNumber intValue];
  2104.   if (curvenum > ncurvestotal || curvenum < 1) {
  2105.     NXBeep();
  2106.     beepError = 11;
  2107.     return self;
  2108.   }
  2109.   [curveColorWell setColor:curvecolors[curvenum-1]];
  2110.   NXPing();            /* make sure it gets displayed */
  2111.   return self;
  2112. }
  2113.  
  2114. // Update the global colorOption variable.  PlotDelegate sends us
  2115. // this message when the color option on the preferences panel is toggled.
  2116. // Adjust all colors.
  2117. - colorOn:(BOOL)onOff
  2118. {
  2119.   int j;
  2120.   
  2121.   if (onOff) {
  2122.     colorOption = YES;
  2123.     backgroundcolor = NX_COLORBLACK;
  2124.     textcolor = NX_COLORWHITE;
  2125.     [self makeCurvesColorful:NULL];
  2126.   }
  2127.   else {
  2128.     colorOption = NO;
  2129.     backgroundcolor = NX_COLORWHITE;
  2130.     textcolor = NX_COLORBLACK;
  2131.     // Now we set all curvecolors to black
  2132.     for (j=0; j < ncurvestotal; j++) {
  2133.       curvecolors[j] = NX_COLORBLACK;
  2134.     }
  2135.   }
  2136.   [textColorWell setColor:textcolor];
  2137.   [backgroundColorWell setColor:backgroundcolor];
  2138.   [curveColorWell setColor:textcolor];
  2139.  
  2140.   return self;
  2141. }
  2142.  
  2143. - fixMatrixColumn:sender    /* The sender is a matrix */
  2144. {
  2145.   int row, col, i;
  2146.  
  2147.   row = [sender selectedRow];
  2148.   col = [sender selectedCol];
  2149.   if (sender == lineMatrix) {
  2150.     [self adjustLineStyleMatrix:col :row];
  2151.     // Now, instead of redisplaying the whole matrix, just redraw the
  2152.     // cells in the affected column.  It's much faster this way.
  2153.     for (i=0; i<N_LINE_STYLES; i++)
  2154.       [lineMatrix drawCellAt:i :col];
  2155.   }
  2156.   else if (sender == symbolMatrix) {
  2157.     [self adjustSymbolTypeMatrix:col :row];
  2158.     // Comment above applies here, too.
  2159.     for (i=0; i<N_SYMBOL_STYLES; i++)
  2160.       [symbolMatrix drawCellAt:i :col];
  2161.   }
  2162.   return self;
  2163. }
  2164.  
  2165. /* change the column which is to be taken as the x data */
  2166. - swapColumns:(int)prev_col :(int)col forFileNumber:(int)i
  2167. {
  2168.   datahunk *pdh;
  2169.   float    *tmp;
  2170.  
  2171.   if (col < 0 || prev_col < 0)    /* be very cautious */
  2172.     return self;
  2173.  
  2174.   if (col==0 && prev_col==0)    /* must avoid this special case */
  2175.     return self;
  2176.  
  2177.   pdh    = (datahunk *)[datahunkArray elementAt:(unsigned)i];
  2178.   if (prev_col == 0) {        /* was first column, generic x-data */
  2179. //  tmp    = pdh->x;        /* why doesn't this order work? */
  2180. //  pdh->x = *(pdh->y + col-1);
  2181. //  *(pdh->y + col-1) = tmp;
  2182.     tmp    = *(pdh->y + col-1);
  2183.     *(pdh->y + col-1) = pdh->x;
  2184.     pdh->x = tmp;
  2185.   }
  2186.   else if (col == 0) {        /* revert back to generic x-data */
  2187.     tmp    = *(pdh->y + prev_col - 1);
  2188.     *(pdh->y + prev_col - 1) = pdh->x;
  2189.     pdh->x = tmp;
  2190.   }
  2191.   else {            /* swapping y's only */
  2192. //    tmp    = *(pdh->y + prev_col - 1);
  2193. //    *(pdh->y + prev_col - 1) = *(pdh->y + col - 1);
  2194. //    *(pdh->y + col - 1) = tmp;
  2195.     tmp    = *(pdh->y + col - 1);
  2196.     *(pdh->y + col - 1)      = *(pdh->y + prev_col - 1);
  2197.     *(pdh->y + prev_col - 1) = pdh->x;
  2198.     pdh->x = tmp;
  2199.   }
  2200.  
  2201.   [self findMinMax:pdh];    /* reset these values here */
  2202.   [self findGlobalMinMax];
  2203.  
  2204.   return self;
  2205. }
  2206.  
  2207.  
  2208. /*
  2209.  * This function is called by the PlotView object during zooming.
  2210.  */
  2211. - stackOldMinMax:(float)xmin :(float)xmax :(float)ymin :(float)ymax
  2212. {
  2213.   oldMin = currentMin;        /* structure assignment */
  2214.   oldMax = currentMax;
  2215.   oldInc = currentInc;        /* have to deal with the increments, too */
  2216.   currentMin.x = xmin;
  2217.   currentMax.x = xmax;
  2218.   currentMin.y = ymin;
  2219.   currentMax.y = ymax;
  2220.   currentInc.x = [xLimits floatValueAt:0];
  2221.   currentInc.y = [yLimits floatValueAt:0];
  2222.  
  2223.   return self;
  2224. }
  2225.  
  2226. /*
  2227.  * This method is invoked by the "Previous View" button on the
  2228.  * control panel.
  2229.  */
  2230. - previousView:sender
  2231. {
  2232.   NXPoint tmp;
  2233.  
  2234.   [self resetXmin:(double)oldMin.x];
  2235.   [self resetXmax:(double)oldMax.x];
  2236.   [self resetXinc:(double)oldInc.x];
  2237.   [self resetYmin:(double)oldMin.y];
  2238.   [self resetYmax:(double)oldMax.y];
  2239.   [self resetYinc:(double)oldInc.y];
  2240.   tmp        = oldMin;
  2241.   oldMin     = currentMin;
  2242.   currentMin = tmp;
  2243.   tmp        = oldMax;
  2244.   oldMax     = currentMax;
  2245.   currentMax = tmp;
  2246.   tmp        = oldInc;
  2247.   oldInc     = currentInc;
  2248.   currentInc = tmp;
  2249.   [self drawPlotButton:1];    /* display "Plotting" */
  2250.   [canvas display];
  2251.   [self drawPlotButton:0];    /* display "Plot" */
  2252.  
  2253.   return self;
  2254. }
  2255.  
  2256. // Set the colors on the new curves.
  2257. // This might also be called to set _all_ the colors (if the color
  2258. // option on the preferences panel has been toggled).
  2259. - makeCurvesColorful:(datahunk *)pdh
  2260. {
  2261.   int   j;
  2262.   float hue = 0.0;
  2263. #define N_PREDEFINED_COLORS 9
  2264.   NXColor predefined_colors[] = {NX_COLORRED, NX_COLORGREEN, NX_COLORBLUE,
  2265.                      NX_COLORCYAN, NX_COLORYELLOW, NX_COLORMAGENTA,
  2266.                      NX_COLORORANGE, NX_COLORPURPLE, NX_COLORBROWN};
  2267. // Those colors come from /usr/include/appkit/color.h
  2268. //    for (j=0; j<pdh->ncurves + ncurvestotal; j++) {
  2269. //      hue = 0.6667 * (float)((j+1) % MIN(11, ncurvestotal + pdh->ncurves))
  2270. //                       / (float)MIN(10, ncurvestotal + pdh->ncurves);
  2271. //      curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
  2272. //      /* update curve no. on color panel */
  2273. //      [curveNumber setIntValue:j+1];
  2274. //      /* and update the color */
  2275. //      [curveColorWell setColor:curvecolors[j]];
  2276. //    }
  2277. // The preceding has the unfortunate effect of changing colors on an already-plotted
  2278. // curve if a new file is read in.  This is undesirable.  Here is a more
  2279. // primitive method.  Maybe a method based on (repeatable) random number
  2280. // generation would be appropriate.
  2281. //    for (j=0; j<pdh->ncurves; j++) {
  2282. //      hue = 0.6667 * (float)((j+ncurvestotal+1) % 6) / 5.0;
  2283. //      curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
  2284. //      /* update curve no. on color panel */
  2285. //      [curveNumber setIntValue:j+ncurvestotal+1];
  2286. //      /* and update the color */
  2287. //      [curveColorWell setColor:curvecolors[j+ncurvestotal]];
  2288. //    }
  2289. // All right, here is the method based on random numbers.  Maybe this will be OK.
  2290. // The trouble with it is that with srandom(10) [see above -- this was about
  2291. // the best] the first two curves are about the same shade of green.  Now the
  2292. // strategy is to preset the first few curve colors, after that they will be
  2293. // random.
  2294.   if (pdh) {
  2295.     for (j=0; j<pdh->ncurves; j++) {
  2296.       if (j + ncurvestotal < N_PREDEFINED_COLORS) {
  2297.     curvecolors[j + ncurvestotal] = predefined_colors[j + ncurvestotal];
  2298.       }
  2299.       else {
  2300.     hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
  2301.     curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
  2302.       }
  2303.       /* nupdate curve no. on color panel */
  2304.       [curveNumber setIntValue:j+ncurvestotal+1];
  2305.       /* and update the color */
  2306.       [curveColorWell setColor:curvecolors[j+ncurvestotal]];
  2307.     }
  2308.   }
  2309.   else {
  2310.     // Here we set the colors on _all_ the curves.
  2311.     for (j=0; j<ncurvestotal; j++) {
  2312.       if (j < N_PREDEFINED_COLORS) {
  2313.     curvecolors[j] = predefined_colors[j];
  2314.       }
  2315.       else {
  2316.     hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
  2317.     curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
  2318.       }
  2319.     }
  2320.   }    
  2321.   return self;
  2322. }
  2323.  
  2324. @end
  2325.